//! Test case models
//! 
//! This module defines the core test case structures, closely following
//! HttpRunner's design but adapted for the actor-based architecture.

use crate::error::{Error, Result, TestExecutionError};
use crate::models::{config::TestConfig, step::IStep, Value, Timestamp, now, Validatable};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::path::Path;
use uuid::Uuid;

/// Interface for test cases (corresponds to HttpRunner's ITestCase)
#[async_trait]
pub trait ITestCase: Send + Sync {
    /// Get the test case instance
    async fn get_test_case(&self) -> Result<TestCase>;
    
    /// Get the test case name
    fn name(&self) -> &str;
    
    /// Get the test case path (if applicable)
    fn path(&self) -> Option<&str> {
        None
    }
}

/// Main test case structure (corresponds to HttpRunner's TestCase)
#[derive(Serialize, Deserialize)]
pub struct TestCase {
    /// Unique identifier for the test case
    pub id: Uuid,
    
    /// Test case configuration
    pub config: TestConfig,
    
    /// Test steps to execute
    #[serde(skip)]
    pub test_steps: Vec<Box<dyn IStep>>,
    
    /// Creation timestamp
    pub created_at: Timestamp,
    
    /// Last modified timestamp
    pub updated_at: Timestamp,
    
    /// Test case metadata
    pub metadata: TestCaseMetadata,
}

/// Test case metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseMetadata {
    /// Test case author
    pub author: Option<String>,
    
    /// Test case description
    pub description: Option<String>,
    
    /// Test case tags
    pub tags: Vec<String>,
    
    /// Test case category
    pub category: Option<String>,
    
    /// Test case priority
    pub priority: TestPriority,
    
    /// Whether the test case is enabled
    pub enabled: bool,
    
    /// Test case version
    pub version: String,
}

/// Test case priority levels
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum TestPriority {
    Low,
    Medium,
    High,
    Critical,
}

impl Default for TestPriority {
    fn default() -> Self {
        TestPriority::Medium
    }
}

/// Test case definition structure (corresponds to HttpRunner's TestCaseDef)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseDef {
    /// Test case configuration
    pub config: TestConfig,
    
    /// Raw test steps (before conversion to IStep)
    pub steps: Vec<Value>,
    
    /// Test case metadata
    #[serde(default)]
    pub metadata: TestCaseMetadata,
}

/// Test case loaded from file path (corresponds to HttpRunner's TestCasePath)
#[derive(Debug, Clone)]
pub struct TestCasePath {
    /// File path to the test case
    pub path: String,
    
    /// Cached test case (loaded lazily)
    cached_test_case: Option<TestCase>,
}

/// Test case loaded from JSON string (corresponds to HttpRunner's TestCaseJSON)
#[derive(Debug, Clone)]
pub struct TestCaseJson {
    /// JSON content
    pub json: String,
    
    /// Cached test case (loaded lazily)
    cached_test_case: Option<TestCase>,
}

impl std::fmt::Debug for TestCase {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TestCase")
            .field("id", &self.id)
            .field("config", &self.config)
            .field("test_steps", &format!("{} steps", self.test_steps.len()))
            .field("created_at", &self.created_at)
            .field("updated_at", &self.updated_at)
            .field("metadata", &self.metadata)
            .finish()
    }
}

impl Clone for TestCase {
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            config: self.config.clone(),
            test_steps: Vec::new(), // Cannot clone trait objects
            created_at: self.created_at,
            updated_at: self.updated_at,
            metadata: self.metadata.clone(),
        }
    }
}

impl TestCase {
    /// Create a new test case
    pub fn new(config: TestConfig) -> Self {
        let now = now();
        Self {
            id: Uuid::new_v4(),
            config,
            test_steps: Vec::new(),
            created_at: now,
            updated_at: now,
            metadata: TestCaseMetadata::default(),
        }
    }
    
    /// Add a test step
    pub fn add_step(&mut self, step: Box<dyn IStep>) {
        self.test_steps.push(step);
        self.updated_at = now();
    }
    
    /// Get the number of test steps
    pub fn step_count(&self) -> usize {
        self.test_steps.len()
    }
    
    /// Check if the test case is valid
    pub fn validate(&self) -> Result<()> {
        if self.config.name.is_empty() {
            return Err(Error::TestExecution(TestExecutionError::InvalidTestCase {
                reason: "Test case name cannot be empty".to_string(),
            }));
        }
        
        if self.test_steps.is_empty() {
            return Err(Error::TestExecution(TestExecutionError::InvalidTestCase {
                reason: "Test case must have at least one step".to_string(),
            }));
        }
        
        // Validate configuration
        self.config.validate()?;
        
        Ok(())
    }
    
    /// Update metadata
    pub fn update_metadata(&mut self, metadata: TestCaseMetadata) {
        self.metadata = metadata;
        self.updated_at = now();
    }
    
    /// Check if the test case is enabled
    pub fn is_enabled(&self) -> bool {
        self.metadata.enabled
    }
    
    /// Get test case tags
    pub fn tags(&self) -> &[String] {
        &self.metadata.tags
    }
    
    /// Add a tag
    pub fn add_tag(&mut self, tag: String) {
        if !self.metadata.tags.contains(&tag) {
            self.metadata.tags.push(tag);
            self.updated_at = now();
        }
    }
    
    /// Remove a tag
    pub fn remove_tag(&mut self, tag: &str) {
        self.metadata.tags.retain(|t| t != tag);
        self.updated_at = now();
    }
}

impl Default for TestCaseMetadata {
    fn default() -> Self {
        Self {
            author: None,
            description: None,
            tags: Vec::new(),
            category: None,
            priority: TestPriority::default(),
            enabled: true,
            version: "1.0.0".to_string(),
        }
    }
}

impl TestCasePath {
    /// Create a new test case path
    pub fn new<P: AsRef<Path>>(path: P) -> Self {
        Self {
            path: path.as_ref().to_string_lossy().to_string(),
            cached_test_case: None,
        }
    }
    
    /// Load test case from file
    async fn load_from_file(&self) -> Result<TestCase> {
        let content = tokio::fs::read_to_string(&self.path).await
            .map_err(|e| Error::TestExecution(TestExecutionError::TestCaseNotFound {
                path: self.path.clone(),
            }))?;
        
        // Determine file format based on extension
        let test_case_def: TestCaseDef = if self.path.ends_with(".yaml") || self.path.ends_with(".yml") {
            serde_yaml::from_str(&content)
                .map_err(|e| Error::TestExecution(TestExecutionError::InvalidTestCase {
                    reason: format!("Invalid YAML format: {}", e),
                }))?
        } else {
            serde_json::from_str(&content)
                .map_err(|e| Error::TestExecution(TestExecutionError::InvalidTestCase {
                    reason: format!("Invalid JSON format: {}", e),
                }))?
        };
        
        // Convert TestCaseDef to TestCase
        self.convert_def_to_case(test_case_def).await
    }
    
    /// Convert TestCaseDef to TestCase
    async fn convert_def_to_case(&self, def: TestCaseDef) -> Result<TestCase> {
        let mut test_case = TestCase::new(def.config);
        test_case.metadata = def.metadata;
        
        // TODO: Convert steps from Value to IStep implementations
        // This will be implemented when we create the step implementations
        
        Ok(test_case)
    }
}

impl TestCaseJson {
    /// Create a new test case from JSON
    pub fn new(json: String) -> Self {
        Self {
            json,
            cached_test_case: None,
        }
    }
    
    /// Load test case from JSON
    async fn load_from_json(&self) -> Result<TestCase> {
        let test_case_def: TestCaseDef = serde_json::from_str(&self.json)
            .map_err(|e| Error::TestExecution(TestExecutionError::InvalidTestCase {
                reason: format!("Invalid JSON format: {}", e),
            }))?;
        
        // Convert TestCaseDef to TestCase
        self.convert_def_to_case(test_case_def).await
    }
    
    /// Convert TestCaseDef to TestCase
    async fn convert_def_to_case(&self, def: TestCaseDef) -> Result<TestCase> {
        let mut test_case = TestCase::new(def.config);
        test_case.metadata = def.metadata;
        
        // TODO: Convert steps from Value to IStep implementations
        // This will be implemented when we create the step implementations
        
        Ok(test_case)
    }
}

#[async_trait]
impl ITestCase for TestCase {
    async fn get_test_case(&self) -> Result<TestCase> {
        Ok((*self).clone())
    }
    
    fn name(&self) -> &str {
        &self.config.name
    }
}

#[async_trait]
impl ITestCase for TestCasePath {
    async fn get_test_case(&self) -> Result<TestCase> {
        if let Some(ref cached) = self.cached_test_case {
            Ok((*cached).clone())
        } else {
            let test_case = self.load_from_file().await?;
            Ok(test_case)
        }
    }
    
    fn name(&self) -> &str {
        Path::new(&self.path)
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown")
    }
    
    fn path(&self) -> Option<&str> {
        Some(&self.path)
    }
}

#[async_trait]
impl ITestCase for TestCaseJson {
    async fn get_test_case(&self) -> Result<TestCase> {
        if let Some(ref cached) = self.cached_test_case {
            Ok((*cached).clone())
        } else {
            let test_case = self.load_from_json().await?;
            Ok(test_case)
        }
    }
    
    fn name(&self) -> &str {
        "json_test_case"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::config::TestConfig;

    #[test]
    fn test_test_case_creation() {
        let config = TestConfig::new("test_case".to_string());
        let test_case = TestCase::new(config);
        
        assert_eq!(test_case.config.name, "test_case");
        assert_eq!(test_case.step_count(), 0);
        assert!(test_case.is_enabled());
    }

    #[test]
    fn test_test_case_validation() {
        let mut config = TestConfig::new("".to_string());
        let test_case = TestCase::new(config);
        
        // Should fail validation due to empty name
        assert!(test_case.validate().is_err());
    }

    #[test]
    fn test_test_case_tags() {
        let config = TestConfig::new("test_case".to_string());
        let mut test_case = TestCase::new(config);
        
        test_case.add_tag("smoke".to_string());
        test_case.add_tag("api".to_string());
        
        assert_eq!(test_case.tags().len(), 2);
        assert!(test_case.tags().contains(&"smoke".to_string()));
        assert!(test_case.tags().contains(&"api".to_string()));
        
        test_case.remove_tag("smoke");
        assert_eq!(test_case.tags().len(), 1);
        assert!(!test_case.tags().contains(&"smoke".to_string()));
    }
}
